import sys
import os
import re
import unicodedata
import simplejson
import getopt

import Image

from decimal import Decimal

from tesseract import image_to_boxes
from tesseract import image_to_string

# if packaged with tesseract, we need to be in the same directory
this_directory = os.path.realpath(os.path.dirname(sys.argv[0]))
os.chdir(os.path.abspath(this_directory))

class DecimalJSONEncoder(simplejson.JSONEncoder):
	"""																  
	JSONEncoder subclass that knows how to encode decimal types.																	  
	"""	
	def default(self, o):		
		if isinstance(o, Decimal):
			return float(o)
		else:
			return super(DecimalJSONEncoder, self).default(o)


def truncate_float_as_decimal(f, multiplier=1000000.0):
	multiplier_decimal = Decimal(str(multiplier))
	f_decimal = Decimal(str(f))
	return Decimal(str(round(f_decimal * multiplier_decimal)))/multiplier_decimal
	
def generate_ocr(image_path, language=None, indent=False):
	
	ocr_image = Image.open(image_path)	
	ocr_image_width, ocr_image_height = ocr_image.size
		
	boxes = image_to_boxes(ocr_image, lang=language)
	text = image_to_string(ocr_image, lang=language)
	
	# must be interpreted the same on all sides and throughout processes
	# KEEP IT SIMPLE!!
	
	word_re = re.compile(u'[^\s\W]+')
	white_re = re.compile(u'[\s]+')
	# preserve high order characters
	#text = text.decode('utf8')
	#boxes = boxes.decode('utf8')
	# * OR *
	# normalize strange characters to ascii (better for english)
	text = unicodedata.normalize('NFKD', text.decode('utf8')).encode('ascii','ignore')	
	boxes = unicodedata.normalize('NFKD', boxes.decode('utf8')).encode('ascii','ignore')
	
	# whitespace is sneaking into boxes somehow, it needs to be removed in order to resolve words correctly
	# otherwise words and boxes lose sync. a better solution will be to do a single pass when it becomes 
	# available in tesseract -> http://code.google.com/p/tesseract-ocr/issues/detail?id=59&q=boxes.
	boxes_split = [box for box in boxes.split(u'\n') if box[0] != ' ']
	boxes_string = u''.join([b[0] for b in boxes_split])
	
	positions = []
	boxes_index_current = 0
	
	for word_match in word_re.finditer(text):
		
		word = word_match.group()
		word_index_start, word_index_end = word_match.span()
		
		try:
			word_index = boxes_string.find(word,boxes_index_current)
			#print '%s %s %s' % (word, boxes_index_current, word_index)
			if word_index >= 0:
				
				boxes_index_start = word_index
				boxes_index_end =  word_index + len(word)
				
				characters = boxes_split[boxes_index_start:boxes_index_end]
				
				# character left bottom right top
				# tesseract coordinate space is bottom/left
				teseract_coordinates = {}
				teseract_coordinates['left'] = float("infinity")
				teseract_coordinates['right'] = 0
				teseract_coordinates['top'] = 0
				teseract_coordinates['bottom'] = float("infinity")
				for char in characters:
					char_split = [float(c) for c in char.split(' ')[1:]]
					teseract_coordinates['left'] = min(teseract_coordinates['left'],char_split[0])
					teseract_coordinates['right'] = max(teseract_coordinates['right'],char_split[2])
					teseract_coordinates['top'] = max(teseract_coordinates['top'],char_split[3])
					teseract_coordinates['bottom'] = min(teseract_coordinates['bottom'],char_split[1])
				
				# change the coordinate space to top left and use fractional positioning
				# css style ordering: top, right, bottom, left
				tlfp = [0,0,0,0]
				tlfp[0] = (ocr_image_height - teseract_coordinates['top'])/ocr_image_height			
				tlfp[1] = teseract_coordinates['right']/ocr_image_width			
				tlfp[2] = (ocr_image_height - teseract_coordinates['bottom'])/ocr_image_height
				tlfp[3] = teseract_coordinates['left']/ocr_image_width
				
				# avoid +/-infinity division errors
				for i, coordinate in enumerate(tlfp):
					if coordinate < 0.0:
						tlfp[i] = Decimal('0.0')
					elif coordinate > 1.1:
						tlfp[i] = Decimal('1.0')
				
				position = [[truncate_float_as_decimal(d) for d in tlfp],[word_index_start, word_index_end]]
				positions.append(position)
				
				boxes_index_current = boxes_index_end
			
			else:
				# dangerous to ignore, but not worth raising an error
				# might be worth logging here
				continue
				
		except UnicodeEncodeError:			
			# this will occur without ascii normalization given some
			# characters that are handed back from tesseract
			# unsure how and if to handle oter than to move along
			continue
	
		
	ocr_data = {}
	ocr_data['_meta_'] = {"application":"keenerocr","version":1.0}
	ocr_data['ocr'] = {}
	ocr_data['ocr']['text'] = text
	ocr_data['ocr']['positions'] = positions
	
	
	try:
		# requires simplejson 2.1 to handle decimal natively
		json = simplejson.dumps(ocr_data, use_decimal=True,indent=2 if indent else None)
	except TypeError:
		# decimal hack for older versions
		json = simplejson.dumps(ocr_data, default=DecimalJSONEncoder().default, indent=2 if indent else None)
			
	return json

def usage():
	sys.stderr.write('\n  Usage: keenerocr image_file [optional args]\n    arguments\n    --lang: language to pass to tesseract [--lang=eng]\n    --indent: json indentation on [--indent]\n')
	sys.exit(2)
	
if __name__ == '__main__':
		
	try:
		#a = ['-lang=', '--testing', '--output-file', 'abc.def', '-x', 'a1', 'a2']
		opts, args = getopt.getopt(sys.argv[1:], "", ['lang=','indent='])
		if len(args) < 1:
			usage()
			sys.exit(1)
		image_path = args[0]
		language = 'eng'
		indent = False
		for o, a in opts:
			if o == "--lang":
				language = a
			elif o in ("--indent"):
				indent = True			
			else:
				assert False, "unhandled option"
				usage()
		
		try:
			json = generate_ocr(image_path, language=language, indent=indent)
			sys.stdout.write(json)
			
		except IOError:
			sys.stderr.write('ERROR: Could not open file "%s"\n' % image_path)
			sys.exit(2)
			
	except getopt.GetoptError, err:
		# print help information and exit:
		usage()
		sys.exit(1)

	